// es5版本的promise

function resolvePromise(x, promise, resolve, reject) {
  if (x === promise) {
    return reject(
      new TypeError('TypeError: Chaining cycle detected for promise #<Promise>')
    );
  }
  var called = false;
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      var then = x.then;
      if (typeof then === 'function') {
        then.call(
          x,
          function (data) {
            if (called) return;
            called = true;
            resolvePromise(data, promise, resolve, reject);
          },
          function (err) {
            if (called) return;
            called = true;
            reject(err);
          }
        );
      } else {
        if (called) return;
        called = true;
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    if (called) return;
    called = true;
    resolve(x);
  }
}
function Promise(executor) {
  this.status = 'pending'; // 默认是等待状态
  this.value = ''; // 成功的值
  this.reason = ''; // 失败的原因
  this.onResolveCallbacks = []; // 收集成功的回调
  this.onRejectCallbacks = []; // 收集失败的回调
  var that = this;
  function resolve(value) {
    if (value instanceof Promise) {
      return value.then(resolve, reject);
    }
    if (that.status === 'pending') {
      that.value = value;
      that.status = 'fulfilled';
      that.onResolveCallbacks.forEach(function (fn) {
        fn();
      });
    }
  }
  function reject(reason) {
    if (that.status === 'pending') {
      that.reason = reason;
      that.status = 'rejected';
      that.onRejectCallbacks.forEach(function (fn) {
        fn();
      });
    }
  }
  try {
    executor(resolve, reject);
  } catch (e) {
    reject(e);
  }
}
Promise.prototype.then = function (onfulfilled, onrejected) {
  onfulfilled =
    typeof onfulfilled === 'function'
      ? onfulfilled
      : function (value) {
          return value;
        };
  onrejected =
    typeof onrejected === 'function'
      ? onrejected
      : function (err) {
          throw err;
        };
  var that = this;
  var promise2 = new Promise(function (resolve, reject) {
    if (that.status === 'fulfilled') {
      setTimeout(function () {
        try {
          var x = onfulfilled(that.value);
          resolvePromise(x, promise2, resolve, reject);
        } catch (e) {
          reject(e);
        }
      }, 0);
    } else if (that.status === 'rejected') {
      setTimeout(function () {
        try {
          var x = onrejected(that.reason);
          resolvePromise(x, promise2, resolve, reject);
        } catch (e) {
          reject(e);
        }
      }, 0);
    } else {
      that.onResolveCallbacks.push(function () {
        setTimeout(function () {
          try {
            var x = onfulfilled(that.value);
            resolvePromise(x, promise2, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      });
      that.onRejectCallbacks.push(function () {
        setTimeout(function () {
          try {
            var x = onrejected(that.reason);
            resolvePromise(x, promise2, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      });
    }
  });
  return promise2;
};
Promise.prototype.catch = function (onrejected) {
  return this.then(null, onrejected);
};
Promise.prototype.finally = function (callback) {
  return this.then(
    function (res) {
      return Promise.resolve(callback()).then(function () {
        return res;
      });
    },
    function (err) {
      return Promise.resolve(callback()).then(function () {
        throw err;
      });
    }
  );
};
Promise.resolve = function (value) {
  return new Promise(function (resolve, reject) {
    resolve(value);
  });
};
Promise.reject = function (reason) {
  return new Promise(function (resolve, reject) {
    reject(reason);
  });
};
Promise.all = function (values) {
  return new Promise(function (resolve, reject) {
    var result = [],
      count = 0;
    if(!values.length) {
      resolve([]);
      return;
    }
    function setData(key, value) {
      result[key] = value;
      if (++count === values.length) {
        resolve(result);
      }
    }
    for (var i = 0; i < values.length; i++) {
      (function (i) {
        var current = values[i];
        if (current && typeof current.then === 'function') {
          current.then(
            function (data) {
              setData(i, data);
            },
            function (e) {
              reject(e);
            }
          );
        } else {
          setData(i, current);
        }
      })(i);
    }
  });
};
Promise.allSettled = function (values) {
  return new Promise(function (resolve, reject) {
    var result = [],
      count = 0;
    function setData(key, value) {
      result[key] = value;
      if (++count === values.length) {
        resolve(result);
      }
    }
    for (var i = 0; i < values.length; i++) {
      (function (i) {
        var current = values[i];
        if (current && typeof current.then === 'function') {
          current.then(
            function (data) {
              setData(i, { status: 'fulfilled', value: data });
            },
            function (err) {
              setData(i, { status: 'rejected', reason: err });
            }
          );
        } else {
          setData(i, { status: 'fulfilled', value: current });
        }
      })(i);
    }
  });
};
Promise.race = function (values) {
  return new Promise(function (resolve, reject) {
    for (var i = 0; i < values.length; i++) {
      (function (i) {
        var current = values[i];
        if (current && typeof current.then === 'function') {
          current.then(
            function (data) {
              resolve(data);
            },
            function (e) {
              reject(e);
            }
          );
        } else {
          resolve(current);
        }
      })(i);
    }
  });
};
// 测试
Promise.defer = Promise.deferred = function () {
  let dfd = {};
  dfd.promise = new Promise(function (resolve, reject) {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
};
module.exports = Promise;
